﻿Imports System
Imports System.Collections.Generic
Imports System.Collections.Specialized
Imports System.Diagnostics
Imports System.Globalization
Imports System.Runtime.CompilerServices
Imports System.Threading
Imports System.Workflow.Runtime

Namespace System.Workflow.Runtime.Hosting
    Public Class DefaultWorkflowSchedulerService
        Inherits WorkflowSchedulerService
        ' Methods
        Public Sub New()
            Me.New(DefaultWorkflowSchedulerService.DefaultThreadCount)
        End Sub

        Public Sub New(ByVal parameters As NameValueCollection)
            Me.timerQueue = New KeyedPriorityQueue([Of] Guid, CallbackInfo, DateTime)
            If (parameters Is Nothing) Then
                Throw New ArgumentNullException("parameters")
            End If
            Me.maxSimultaneousWorkflows = DefaultWorkflowSchedulerService.DefaultThreadCount
            Dim str As String
            For Each str In parameters.Keys
                If (str Is Nothing) Then
                    Throw New ArgumentException(String.Format(Thread.CurrentThread.CurrentCulture, ExecutionStringManager.UnknownConfigurationParameter, New Object() { "null" }))
                End If
                Dim s As String = parameters.Item(str)
                If Not str.Equals("maxSimultaneousWorkflows", StringComparison.OrdinalIgnoreCase) Then
                    Throw New ArgumentException(String.Format(Thread.CurrentThread.CurrentCulture, ExecutionStringManager.UnknownConfigurationParameter, New Object() { str }))
                End If
                If Not Integer.TryParse(s, NumberStyles.Integer, CultureInfo.CurrentCulture, Me.maxSimultaneousWorkflows) Then
                    Throw New FormatException("maxSimultaneousWorkflows")
                End If
            Next
            If (Me.maxSimultaneousWorkflows < 1) Then
                Throw New ArgumentOutOfRangeException("maxSimultaneousWorkflows", Me.maxSimultaneousWorkflows, String.Empty)
            End If
            Me.init
        End Sub

        Public Sub New(ByVal maxSimultaneousWorkflows As Integer)
            Me.timerQueue = New KeyedPriorityQueue([Of] Guid, CallbackInfo, DateTime)
            If (maxSimultaneousWorkflows < 1) Then
                Throw New ArgumentOutOfRangeException("maxSimultaneousWorkflows", maxSimultaneousWorkflows, String.Empty)
            End If
            Me.maxSimultaneousWorkflows = maxSimultaneousWorkflows
            Me.init
        End Sub

        Protected Friend Overrides Sub Cancel(ByVal timerId As Guid)
            WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Cancelling work with timer ID {0}", New Object() { timerId })
            If (timerId = Guid.Empty) Then
                Throw New ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, New Object() { "timerId" }), "timerId")
            End If
            SyncLock Me.timerQueue
                Me.timerQueue.Remove(timerId)
            End SyncLock
        End Sub

        Private Function CreateTimerCallback(ByVal info As CallbackInfo) As Timer
            Dim utcNow As DateTime = DateTime.UtcNow
            Dim dueTime As TimeSpan = If((info.When > utcNow), DirectCast((info.When - utcNow), TimeSpan), TimeSpan.Zero)
            If (dueTime > DefaultWorkflowSchedulerService.fiveMinutes) Then
                dueTime = DefaultWorkflowSchedulerService.fiveMinutes
            End If
            Return New Timer(Me.timerCallback, info.State, dueTime, DefaultWorkflowSchedulerService.infinite)
        End Function

        Private Sub EnqueueWorkItem(ByVal workItem As WorkItem)
            SyncLock Me.waitingQueue
                Me.waitingQueue.Enqueue(workItem)
                If ((Me.running AndAlso (Me.numCurrentWorkers < Me.maxSimultaneousWorkflows)) AndAlso ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf Me.QueueWorkerProcess))) Then
                    Me.numCurrentWorkers += 1
                End If
            End SyncLock
            If (Not Me.queueCounters Is Nothing) Then
                Dim counter As PerformanceCounter
                For Each counter In Me.queueCounters
                    counter.RawValue = Me.waitingQueue.Count
                Next
            End If
        End Sub

        Private Sub init()
            Me.timerCallback = New TimerCallback(AddressOf Me.OnTimerCallback)
            AddHandler Me.timerQueue.FirstElementChanged, New EventHandler([Of] KeyedPriorityQueueHeadChangedEventArgs([Of] CallbackInfo))(AddressOf Me.OnFirstElementChanged)
            Me.waitingQueue = New Queue([Of] WorkItem)
        End Sub

        Private Sub OnFirstElementChanged(ByVal source As Object, ByVal e As KeyedPriorityQueueHeadChangedEventArgs([Of] CallbackInfo))
            If (Not Me.callbackTimer Is Nothing) Then
                Me.callbackTimer.Dispose
                Me.callbackTimer = Nothing
            End If
            If ((Not e.NewFirstElement Is Nothing) AndAlso (MyBase.State = WorkflowRuntimeServiceState.Started)) Then
                Me.callbackTimer = Me.CreateTimerCallback(e.NewFirstElement)
            End If
        End Sub

        Protected Overrides Sub OnStarted()
            SyncLock Me.timerQueue
                MyBase.OnStarted
                Dim info As CallbackInfo = Me.timerQueue.Peek
                If (Not info Is Nothing) Then
                    Me.callbackTimer = Me.CreateTimerCallback(info)
                End If
                Me.running = True
            End SyncLock
            SyncLock Me.waitingQueue
                Dim num As Integer = Math.Min(Me.maxSimultaneousWorkflows, Me.waitingQueue.Count)
                Dim i As Integer
                For i = 0 To num - 1
                    If ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf Me.QueueWorkerProcess)) Then
                        Me.numCurrentWorkers += 1
                    End If
                Next i
            End SyncLock
            If ((Me.queueCounters Is Nothing) AndAlso (Not MyBase.Runtime.PerformanceCounterManager Is Nothing)) Then
                Me.queueCounters = MyBase.Runtime.PerformanceCounterManager.CreateCounters(ExecutionStringManager.PerformanceCounterWorkflowsWaitingName)
            End If
        End Sub

        Private Sub OnTimerCallback(ByVal ignored As Object)
            Trace.CorrelationManager.ActivityId = Guid.Empty
            Dim info As CallbackInfo = Nothing
            Dim flag As Boolean = False
            Try 
                SyncLock Me.timerQueue
                    If (MyBase.State = WorkflowRuntimeServiceState.Started) Then
                        info = Me.timerQueue.Peek
                        If (Not info Is Nothing) Then
                            If info.IsExpired Then
                                WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Timeout occured for timer for instance {0}", New Object() { info.State })
                                Me.timerQueue.Dequeue
                                flag = True
                            Else
                                Me.callbackTimer = Me.CreateTimerCallback(info)
                            End If
                        End If
                    End If
                End SyncLock
                If (flag AndAlso (Not info Is Nothing)) Then
                    info.Callback.Invoke(info.State)
                End If
            Catch exception1 As WorkflowOwnershipException
            Catch exception As ThreadAbortException
                WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "Timeout for instance, {0} threw exception {1}", New Object() { If((info Is Nothing), Nothing, info.State), exception.Message })
                MyBase.RaiseServicesExceptionNotHandledEvent(exception, DirectCast(info.State, Guid))
                Throw
            Catch exception2 As Exception
                WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "Timeout for instance, {0} threw exception {1}", New Object() { If((info Is Nothing), Nothing, info.State), exception2.Message })
                MyBase.RaiseServicesExceptionNotHandledEvent(exception2, DirectCast(info.State, Guid))
            End Try
        End Sub

        Private Sub QueueWorkerProcess(ByVal state As Object)
            Trace.CorrelationManager.ActivityId = Guid.Empty
            Do While True
                Dim item As WorkItem
                SyncLock Me.waitingQueue
                    If ((Me.waitingQueue.Count = 0) OrElse Not Me.running) Then
                        Me.numCurrentWorkers -= 1
                        Monitor.Pulse(Me.waitingQueue)
                        Return
                    End If
                    item = Me.waitingQueue.Dequeue
                End SyncLock
                If (Not Me.queueCounters Is Nothing) Then
                    Dim counter As PerformanceCounter
                    For Each counter In Me.queueCounters
                        counter.RawValue = Me.waitingQueue.Count
                    Next
                End If
                item.Invoke(Me)
            Loop
        End Sub

        Protected Friend Overrides Sub Schedule(ByVal callback As WaitCallback, ByVal workflowInstanceId As Guid)
            WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Scheduling work for instance {0}", New Object() { workflowInstanceId })
            If (callback Is Nothing) Then
                Throw New ArgumentNullException("callback")
            End If
            If (workflowInstanceId = Guid.Empty) Then
                Throw New ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, New Object() { "workflowInstanceId" }))
            End If
            Me.EnqueueWorkItem(New WorkItem(callback, workflowInstanceId))
        End Sub

        Protected Friend Overrides Sub Schedule(ByVal callback As WaitCallback, ByVal workflowInstanceId As Guid, ByVal whenUtc As DateTime, ByVal timerId As Guid)
            WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Scheduling work for instance {0} on timer ID {1} in {2}", New Object() { workflowInstanceId, timerId, DirectCast((whenUtc - DateTime.UtcNow), TimeSpan) })
            If (callback Is Nothing) Then
                Throw New ArgumentNullException("callback")
            End If
            If (timerId = Guid.Empty) Then
                Throw New ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, New Object() { "timerId" }))
            End If
            If (workflowInstanceId = Guid.Empty) Then
                Throw New ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, New Object() { "workflowInstanceId" }))
            End If
            Dim info As New CallbackInfo(Me, callback, workflowInstanceId, whenUtc)
            SyncLock Me.timerQueue
                Me.timerQueue.Enqueue(timerId, info, whenUtc)
            End SyncLock
        End Sub

        Protected Friend Overrides Sub [Stop]()
            SyncLock Me.timerQueue
                MyBase.Stop
                If (Not Me.callbackTimer Is Nothing) Then
                    Me.callbackTimer.Dispose
                    Me.callbackTimer = Nothing
                End If
                Me.running = False
            End SyncLock
            SyncLock Me.waitingQueue
                Do While (Me.numCurrentWorkers > 0)
                    Monitor.Wait(Me.waitingQueue)
                Loop
            End SyncLock
        End Sub


        ' Properties
        Private Shared ReadOnly Property DefaultThreadCount As Integer
            Get
                If (Environment.ProcessorCount <> 1) Then
                    Return CInt(((5 * Environment.ProcessorCount) * 0.8))
                End If
                Return 5
            End Get
        End Property

        Public ReadOnly Property MaxSimultaneousWorkflows As Integer
            Get
                Return Me.maxSimultaneousWorkflows
            End Get
        End Property


        ' Fields
        Private callbackTimer As Timer
        Private Const DEFAULT_MAX_SIMULTANEOUS_WORKFLOWS As Integer = 5
        Private Shared fiveMinutes As TimeSpan = New TimeSpan(0, 5, 0)
        Private Shared infinite As TimeSpan = New TimeSpan(-1)
        Private Const MAX_SIMULTANEOUS_WORKFLOWS_KEY As String = "maxSimultaneousWorkflows"
        Private ReadOnly maxSimultaneousWorkflows As Integer
        Private numCurrentWorkers As Integer
        Private queueCounters As IList([Of] PerformanceCounter)
        Private running As Boolean ModReq(IsVolatile)
        Private timerCallback As TimerCallback
        Private timerQueue As KeyedPriorityQueue([Of] Guid, CallbackInfo, DateTime)
        Private waitingQueue As Queue([Of] WorkItem)

        ' Nested Types
        Friend Class CallbackInfo
            ' Methods
            Public Sub New(ByVal service As WorkflowSchedulerService, ByVal callback As WaitCallback, ByVal state As Object, ByVal [when] As DateTime)
                Me.service = service
                Me.callback = callback
                Me.state = state
                Me.when = [when]
            End Sub


            ' Properties
            Public ReadOnly Property Callback As WaitCallback
                Get
                    Return Me.callback
                End Get
            End Property

            Public ReadOnly Property IsExpired As Boolean
                Get
                    Return (DateTime.UtcNow >= Me.when)
                End Get
            End Property

            Public ReadOnly Property State As Object
                Get
                    Return Me.state
                End Get
            End Property

            Public ReadOnly Property [When] As DateTime
                Get
                    Return Me.when
                End Get
            End Property


            ' Fields
            Private callback As WaitCallback
            Private service As WorkflowSchedulerService
            Private state As Object
            Private [when] As DateTime
        End Class

        Friend Class WorkItem
            ' Methods
            Public Sub New(ByVal callback As WaitCallback, ByVal state As Object)
                Me.callback = callback
                Me.state = state
            End Sub

            Public Sub Invoke(ByVal service As WorkflowSchedulerService)
                Try 
                    WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Running workflow {0}", New Object() { Me.state })
                    Me.Callback.Invoke(Me.state)
                Catch exception As Exception
                    If WorkflowExecutor.IsIrrecoverableException(exception) Then
                        Throw
                    End If
                    service.RaiseExceptionNotHandledEvent(exception, DirectCast(Me.state, Guid))
                End Try
            End Sub


            ' Properties
            Public ReadOnly Property Callback As WaitCallback
                Get
                    Return Me.callback
                End Get
            End Property


            ' Fields
            Private callback As WaitCallback
            Private state As Object
        End Class
    End Class
End Namespace

